/**
 * Copyright (c) 2012, Brent Mucci, Ken Anderson
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **/

#include "mcl_bitmap.h"
#include <iostream>
#include <fstream>
#include <math.h>

using namespace mcl;

////////////////////////////////////////////////////////
////////////////// Internal Structures /////////////////
////////////////////////////////////////////////////////

// Bitmap file header
struct BitmapFileHeader{
		// Type must always be 'BM' to declare that it is a bmp file.
		char type[2];
		// Size of the file in bytes
		unsigned int size;
		// Ignore, must always be 0
		unsigned short reserved1;
		// Ignore, must always be 0
		unsigned short reserved2;
		// Offset from beginning of file to the bitmap data in bytes, standard value is 1078
		unsigned int offBits;
};

// Bitmap informational header
struct BitmapInfoHeader{
		// Size of the infoHeader in bytes
		unsigned int bitSize;
		// Width of the image **in pixels**
		unsigned int width;
		// Height of the image **in pixels**
		unsigned int height;
		// The number of planes of the target device, must be set to zero. 1 is standard value.
		unsigned short planes;
		// Bits per pixel, 8 is the standard value
		unsigned short bitCount;
		// The type compression, normally 0 for none.
		unsigned int compression;
		// Size of the image data in bytes without compression. 0 is a potential valid value.
		unsigned int size;
		// Horizontal pixels per meter on device. Usually set to 0.
		unsigned int horizontalPerMeter;
		// Vertical pixels per meter on device. Usually set to 0.
		unsigned int verticalPerMeter;
		// Number of colours used in the bitmap, if set to zero the number of colours is calculated using bitCount.
		unsigned int numOfColours;
		// The number of 'important' colours. If set to 0, all are important.
		unsigned int numOfImportant;
};

////////////////////////////////////////////////////////
////////////////// BitmapLoader/Writer /////////////////
////////////////////////////////////////////////////////

bool BitmapLoader::load( const std::string &filename, Image & image ) const
{
	std::ifstream file (filename.c_str(), std::ios::in|std::ios::binary);
	BitmapFileHeader fileHeader;
	BitmapInfoHeader infoHeader;
	unsigned char * imageData = NULL;

	if(file.is_open())
	{
		file.read((char *) &fileHeader.type, 2);
		file.read((char *) &fileHeader.size, 4);
		file.read((char *) &fileHeader.reserved1, 2);
		file.read((char *) &fileHeader.reserved2, 2);
		file.read((char *) &fileHeader.offBits, 4);

		file.read((char *) &infoHeader.bitSize, 4);
		file.read((char *) &infoHeader.width, 4);
		file.read((char *) &infoHeader.height, 4);
		file.read((char *) &infoHeader.planes, 2);
		file.read((char *) &infoHeader.bitCount, 2);
		file.read((char *) &infoHeader.compression, 4);
		file.read((char *) &infoHeader.size, 4);
		file.read((char *) &infoHeader.horizontalPerMeter, 4);
		file.read((char *) &infoHeader.verticalPerMeter, 4);
		file.read((char *) &infoHeader.numOfColours, 4);
		file.read((char *) &infoHeader.numOfImportant, 4);

		file.seekg(fileHeader.offBits, std::ios::beg);
		
		if(infoHeader.size == 0)
		{	// Windows 7 Pro image converter glitch.
			infoHeader.size = fileHeader.size - fileHeader.offBits;
		}
		
		bool success = image.init( infoHeader.width, infoHeader.height);
		if(success)
		{
			success = readData( file, image, infoHeader.size );
		}

		file.close();

		if(success)
		{
			return true;
		}
	}

	return false;
}

// TODO -- deal with different bitsizes, pitches, and other parameters.
bool BitmapLoader::readData( std::ifstream & file, Image & image, const unsigned int dataSize ) const
{
	unsigned char * pSourceBuffer = NULL;
	unsigned char * pDest = image.data();
	int destRowIndex = 0, sourceRowIndex = 0;
	unsigned char red, green, blue;

	// Read in all the image data at once, then convert to correct format.
	// This is necessary because Windows uses mutexes when reading/writing to files, 
	// and thus slows down file IO dramatically.
	pSourceBuffer = new unsigned char[dataSize];
	if(!pSourceBuffer)
	{
		return false;
	}
	file.read((char *)pSourceBuffer, dataSize);

	// Convert to RGBA
	for(unsigned int y = 0; y < image.getHeight(); y++)
	{
		int sourceIndex = sourceRowIndex;
		int destIndex = destRowIndex;

		for(unsigned int x = 0; x < image.getWidth(); x++)
		{
			blue  = pSourceBuffer[sourceIndex    ];
			green = pSourceBuffer[sourceIndex + 1];
			red   = pSourceBuffer[sourceIndex + 2];
			
			pDest[destIndex    ] = red;
			pDest[destIndex + 1] = green;
			pDest[destIndex + 2] = blue;
			pDest[destIndex + 3] = 0xFF;
			
			sourceIndex += 3;
			destIndex   += 4;
		}
		sourceRowIndex += image.getPitch() * 3;
		destRowIndex   += image.getPitch() * 4;
	}

	delete[] pSourceBuffer;
	return true;
}

// Writes a bitmap image to storage.
bool BitmapWriter::write( const std::string &filename, const Image & image ) const
{
	std::ofstream file(filename.c_str(), std::ios::out|std::ios::binary);
	BitmapFileHeader fileHeader;
	BitmapInfoHeader infoHeader;
	unsigned char * imageData = NULL;

	if(file.is_open())
	{
		file.write((char *) &fileHeader.type, 2);
		file.write((char *) &fileHeader.size, 4);
		file.write((char *) &fileHeader.reserved1, 2);
		file.write((char *) &fileHeader.reserved2, 2);
		file.write((char *) &fileHeader.offBits, 4);

		file.write((char *) &infoHeader.bitSize, 4);
		file.write((char *) &infoHeader.width, 4);
		file.write((char *) &infoHeader.height, 4);
		file.write((char *) &infoHeader.planes, 2);
		file.write((char *) &infoHeader.bitCount, 2);
		file.write((char *) &infoHeader.compression, 4);
		file.write((char *) &infoHeader.size, 4);
		file.write((char *) &infoHeader.horizontalPerMeter, 4);
		file.write((char *) &infoHeader.verticalPerMeter, 4);
		file.write((char *) &infoHeader.numOfColours, 4);
		file.write((char *) &infoHeader.numOfImportant, 4);

		writeData(file, image);

		file.close();
		return true;
	}
	return false;
}

bool BitmapWriter::writeData( std::ofstream & file, const Image & image ) const
{
	const unsigned char * pSource = image.data();
	int sourceIndex = 0;
	unsigned int bitmapData = 0;

	for(unsigned int y = 0; y < image.getHeight(); y++)
	{
		for(unsigned int x = 0; x < image.getWidth(); x++)
		{
			// Red, Green, Blue
			bitmapData = (pSource[sourceIndex] << 24) | (pSource[sourceIndex + 1] << 16) | (pSource[sourceIndex] << 8) | 255;

			file.write((char *)&bitmapData,4);
		}
	}
	return true;
}
